﻿using System;
using System.Web;
using System.Web.UI;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Data;
using System.Configuration;

using BizElements.BusinessLayer;
using BizElements.Web;

namespace BizElements.Web.UI
{
    /// <summary>Gets search provider specified in URL from the registry to search and manage data.
    /// Same as SearchFrm.aspx but it doesn't have a master page.</summary>
    public partial class LookupFrm : System.Web.UI.Page
    {
        /// <summary>Initializes Page.UICulture to match culture defined in UserSession.UiCulture.</summary>
        protected override void InitializeCulture()
        {
            this.UICulture = GetUiCulture().Name;
        }

        #region Fields. Search components.

        // Field/member names may not be changed since they are saved in AppSettings(see SearchOptionFrm.aspx).
        SearchProvider provider;
        ISearchFilter uiFilter;
        ISearchView uiView;
        ISearchEngine engine;
        DataView searchResults;
        CommandToolBar uiCommandButtons;
        IDictionary<string, object> commandButtonsSettings;
        CommandMenu uiContextMenu;
        IDictionary<string, object> contextMenuSettings;
        protected string JsEnableAllCommands = "";
        string defaultSortBy;

        #endregion

        #region QueryKeys.

        /// <summary>
        /// Adds query parameters to URL which specify a begin value for a field.
        /// </summary>
        /// <param name="relativeFormUrl">URL which opens this page, to which the query is added.</param>
        /// <param name="searchFieldName">Search field for which the begin value is specified.</param>
        /// <param name="beginValue">Field value.</param>
        /// <returns>Original URL to which the query parameter is appended.</returns>
        public static string AddBeginValueToUrl(string url, string searchFieldName, string beginValue)
        {
            string newUrl = url + WebUtil.GetNextUrlOperator(url) + GetBeginValueQueryKey(searchFieldName) + "=" + DoubleUrlEncode(beginValue);
            return newUrl;
        }

        /// <summary>
        /// Gets query key through which a begin value for a field can be specified.
        /// </summary>
        /// <param name="searchFieldName">Search field for which the begin value is specified.</param>
        /// <returns>Query key. Eg. "searchField.EmployeeId".</returns>
        public static string GetBeginValueQueryKey(string searchFieldName)
        {
            return searchFieldDot + searchFieldName;
        }

        /// <summary>
        /// Adds query parameters to URL which specify an end value for a field.
        /// </summary>
        /// <param name="relativeFormUrl">URL which opens this page, to which the query is added.</param>
        /// <param name="searchFieldName">Search field for which the end value is specified.</param>
        /// <param name="endValue">Field value.</param>
        /// <returns>Original URL to which the query parameter is appended.</returns>
        public static string AddEndValueToUrl(string url, string searchFieldName, string endValue)
        {
            string newUrl = url + WebUtil.GetNextUrlOperator(url) + GetEndValueQueryKey(searchFieldName) + "=" + DoubleUrlEncode(endValue);
            return newUrl;
        }

        /// <summary>
        /// Gets query key through which an end value for a field can be specified.
        /// </summary>
        /// <param name="searchFieldName">Search field for which the end value is specified.</param>
        /// <returns>Query key. Eg. "searchField.EmployeeId.EndValue".</returns>
        public static string GetEndValueQueryKey(string searchFieldName)
        {
            return searchFieldDot + searchFieldName + dotEndValue;
        }

        /// <summary>
        /// Adds query parameters to URL which specify a valueset key for a field.
        /// </summary>
        /// <param name="relativeFormUrl">URL which opens this page, to which the query is added.</param>
        /// <param name="searchFieldName">Search field for which the begin value is specified.</param>
        /// <param name="valueSetKey">Valueset key.</param>
        /// <returns>Original URL to which the query parameter is appended.</returns>
        public static string AddSelectedValueSetKeyToUrl(string url, string searchFieldName, string valueSetKey)
        {
            string newUrl = url + WebUtil.GetNextUrlOperator(url) + GetSelectedValueSetQueryKey(searchFieldName) + "=" + DoubleUrlEncode(valueSetKey);
            return newUrl;
        }

        /// <summary>
        /// Gets query key through which a valueset key for a field can be specified.
        /// </summary>
        /// <param name="searchFieldName">Search field for which the begin value is specified.</param>
        /// <returns>Query key. Eg. "searchField.EmployeeId.SelectedValueSetKey".</returns>
        public static string GetSelectedValueSetQueryKey(string searchFieldName)
        {
            return searchFieldDot + searchFieldName + dotSelectedValueSetKey;
        }

        /// <summary>
        /// Adds query parameter to URL which specify values which a field must not contain.
        /// </summary>
        /// <param name="relativeFormUrl">URL which opens this page, to which the query is added.</param>
        /// <param name="searchFieldName">Search field for which the values are specified.</param>
        /// <param name="values">Negated field values.</param>
        /// <returns>Original URL to which the query parameter is appended.</returns>
        public static string AddNotInValuesToUrl(string url, string searchFieldName, Array values)
        {
            if (ArrayUtil.IsNullOrEmpty(values))
                return url;

            string semicolDelimitedValues = HttpUtility.UrlEncode(ArrayUtil.Concat(values, notInValuesDelimiter.ToString()));
            string newUrl = url + WebUtil.GetNextUrlOperator(url) + GetNotInValuesQueryKey(searchFieldName) + "=" + semicolDelimitedValues;
            return newUrl;
        }

        /// <summary>
        /// Gets query key through which values which must not contain can be specified.
        /// </summary>
        /// <param name="searchFieldName">Search field for which the values are specified.</param>
        /// <returns>Query key. Eg. "searchField.EmployeeId.NotInValues".</returns>
        public static string GetNotInValuesQueryKey(string searchFieldName)
        {
            return searchFieldDot + searchFieldName + dotNotInValues;
        }

        /// <summary>
        /// Adds query parameters to URL which specify that a field filter is to be inverted.
        /// </summary>
        /// <param name="relativeFormUrl">URL which opens this page, to which the query is added.</param>
        /// <param name="searchFieldName">Search field for which the begin value is specified.</param>
        /// <returns>Original URL to which the query parameter is appended.</returns>
        public static string AddNegateToUrl(string url, string searchFieldName)
        {
            string newUrl = url + WebUtil.GetNextUrlOperator(url) + GetNegateQueryKey(searchFieldName) + "=" + true.ToString();
            return newUrl;
        }

        /// <summary>
        /// Gets query key which specifies that a field filter is to be inverted.
        /// </summary>
        /// <param name="searchFieldName">Search field for which the begin value is specified.</param>
        /// <returns>Query key. Eg. "searchField.EmployeeId.Negate".</returns>
        public static string GetNegateQueryKey(string searchFieldName)
        {
            return searchFieldDot + searchFieldName + dotNegate;
        }

        /// <summary>
        /// Adds query parameter to URL which specifies multiple values for a field.
        /// </summary>
        /// <param name="relativeFormUrl">URL which opens this page, to which the query is added.</param>
        /// <param name="searchFieldName">Search field for which the values are specified.</param>
        /// <param name="values">Field values.</param>
        /// <returns>Original URL to which the query parameter is appended.</returns>
        public static string AddValuesToUrl(string url, string searchFieldName, Array values)
        {
            if (ArrayUtil.IsNullOrEmpty(values))
                return url;

            string semicolDelimitedValues = HttpUtility.UrlEncode(ArrayUtil.Concat(values, valuesDelimiter.ToString()));
            string newUrl = url + WebUtil.GetNextUrlOperator(url) + GetValuesQueryKey(searchFieldName) + "=" + semicolDelimitedValues;
            return newUrl;
        }

        /// <summary>
        /// Gets query key through which multiple values for a field are specified.
        /// </summary>
        /// <param name="searchFieldName">Search field for which the values are specified.</param>
        /// <returns>Query key. Eg. "searchField.EmployeeId.Values".</returns>
        public static string GetValuesQueryKey(string searchFieldName)
        {
            return searchFieldDot + searchFieldName + dotValues;
        }

        /// <summary>
        /// Query key ("defaultSearchField") through which a begin value for the default/first field can be specified.
        /// </summary>
        public const string DefaultSearchFieldQueryKey = "defaultSearchField";

        private struct QueryKeys
        {
            public const string ProviderKey = "providerKey";
            public const string FilterState = "filterState";
        }

        private static string DoubleUrlEncode(string s)
        {
            return HttpUtility.UrlEncode(HttpUtility.UrlEncode(s));
        }

        private string ProviderKey
        {
            get { return Request.QueryString[QueryKeys.ProviderKey]; }
        }

        private string FilterState
        {
            get { return HttpUtility.UrlDecode(Request.QueryString[QueryKeys.FilterState]); }
        }

        const string searchFieldDot = "searchField.";
        static readonly int searchFieldDotLen = searchFieldDot.Length;
        const string dotBeginValue = ".BeginValue";
        static readonly int dotBeginValueLen = dotBeginValue.Length;
        const string dotSelectedValueSetKey = ".SelectedValueSetKey";
        static readonly int dotSelectedValueSetKeyLen = dotSelectedValueSetKey.Length;
        const string dotNotInValues = ".NotInValues";
        static readonly int dotNotInValuesLen = dotNotInValues.Length;
        const char notInValuesDelimiter = ',';
        const string dotEndValue = ".EndValue";
        static readonly int dotEndValueLen = dotEndValue.Length;
        const string dotNegate = ".Negate";
        static readonly int dotNegateLen = dotNegate.Length;
        const string dotValues = ".Values";
        static readonly int dotValuesLen = dotValues.Length;
        const char valuesDelimiter = ',';

        Dictionary<string, string> fieldBeginValues;
        Dictionary<string, string> fieldSelectedValueSetKeys;
        Dictionary<string, string[]> fieldNotInValues;
        Dictionary<string, string> fieldEndValues;
        Dictionary<string, bool> fieldNegations;
        Dictionary<string, string[]> fieldValues;

        private void EnsureQueryStringFilterIsParsed()
        {
            if (this.fieldBeginValues == null || this.fieldSelectedValueSetKeys == null || this.fieldNotInValues == null || this.fieldEndValues == null || this.fieldNegations == null || this.fieldValues == null)
            {
                this.fieldBeginValues = new Dictionary<string, string>();
                this.fieldSelectedValueSetKeys = new Dictionary<string, string>();
                this.fieldNotInValues = new Dictionary<string, string[]>();
                this.fieldEndValues = new Dictionary<string, string>();
                this.fieldNegations = new Dictionary<string, bool>();
                this.fieldValues = new Dictionary<string, string[]>();

                string[] queryKeys = Request.QueryString.AllKeys;
                foreach (string key in queryKeys)
                {
                    // BeginValue suffix is optional.
                    bool isFilterInitializationParam = key.StartsWith(searchFieldDot, true, CultureInfo.InvariantCulture);
                    if (isFilterInitializationParam)
                    {
                        string fieldNameAndProperty = key.Substring(searchFieldDotLen);
                        string fieldName = null;
                        string property = null;
                        if (!fieldNameAndProperty.Contains("."))
                        {
                            // Property not specified. BeginValue is default.
                            fieldName = fieldNameAndProperty;
                            property = "BeginValue";
                        }
                        else if (fieldNameAndProperty.EndsWith(dotBeginValue))
                        {
                            fieldName = fieldNameAndProperty.Substring(0, fieldNameAndProperty.Length - dotBeginValueLen);
                            property = "BeginValue";
                        }
                        else if (fieldNameAndProperty.EndsWith(dotSelectedValueSetKey))
                        {
                            fieldName = fieldNameAndProperty.Substring(0, fieldNameAndProperty.Length - dotSelectedValueSetKeyLen);
                            property = "SelectedValueSetKey";
                        }
                        else if (fieldNameAndProperty.EndsWith(dotNotInValues))
                        {
                            fieldName = fieldNameAndProperty.Substring(0, fieldNameAndProperty.Length - dotNotInValuesLen);
                            property = "NotInValues";
                        }
                        else if (fieldNameAndProperty.EndsWith(dotEndValue))
                        {
                            fieldName = fieldNameAndProperty.Substring(0, fieldNameAndProperty.Length - dotEndValueLen);
                            property = "EndValue";
                        }
                        else if (fieldNameAndProperty.EndsWith(dotNegate))
                        {
                            fieldName = fieldNameAndProperty.Substring(0, fieldNameAndProperty.Length - dotNegateLen);
                            property = "Negate";
                        }
                        else if (fieldNameAndProperty.EndsWith(dotValues))
                        {
                            fieldName = fieldNameAndProperty.Substring(0, fieldNameAndProperty.Length - dotValuesLen);
                            property = "Values";
                        }

                        if (!string.IsNullOrEmpty(fieldName) && !string.IsNullOrEmpty(property))
                        {
                            switch (property)
                            {
                                case "BeginValue":
                                    this.fieldBeginValues[fieldName] = Request.QueryString[key];
                                    break;
                                case "SelectedValueSetKey":
                                    this.fieldSelectedValueSetKeys[fieldName] = Request.QueryString[key];
                                    break;
                                case "NotInValues":
                                    string semicolDelimitedNotInValues = Request.QueryString[key];
                                    if (!string.IsNullOrEmpty(semicolDelimitedNotInValues))
                                        this.fieldNotInValues[fieldName] = semicolDelimitedNotInValues.Split(new char[] { notInValuesDelimiter }, StringSplitOptions.RemoveEmptyEntries);
                                    break;
                                case "EndValue":
                                    this.fieldEndValues[fieldName] = Request.QueryString[key];
                                    break;
                                case "Negate":
                                    this.fieldNegations[fieldName] = true;
                                    break;
                                case "Values":
                                    string semicolDelimitedValues = Request.QueryString[key];
                                    if (!string.IsNullOrEmpty(semicolDelimitedValues))
                                        this.fieldValues[fieldName] = semicolDelimitedValues.Split(new char[] { valuesDelimiter }, StringSplitOptions.RemoveEmptyEntries);
                                    break;
                            }
                        }
                    }
                }
            }
        }

        private Dictionary<string, string> BeginValues
        {
            get
            {
                EnsureQueryStringFilterIsParsed();
                return this.fieldBeginValues;
            }
        }

        private Dictionary<string, string> SelectedValueSetKeys
        {
            get
            {
                EnsureQueryStringFilterIsParsed();
                return this.fieldSelectedValueSetKeys;
            }
        }

        private Dictionary<string, string[]> NotInValues
        {
            get
            {
                EnsureQueryStringFilterIsParsed();
                return this.fieldNotInValues;
            }
        }

        private Dictionary<string, string> EndValues
        {
            get
            {
                EnsureQueryStringFilterIsParsed();
                return this.fieldEndValues;
            }
        }

        private Dictionary<string, bool> Negations
        {
            get
            {
                EnsureQueryStringFilterIsParsed();
                return this.fieldNegations;
            }
        }

        private Dictionary<string, string[]> Values
        {
            get
            {
                EnsureQueryStringFilterIsParsed();
                return this.fieldValues;
            }
        }

        private object GetDefaultFieldBeginValue()
        {
            ISearchEngineField firstField = (engine != null && engine.SearchFields.Count > 0) ? engine.SearchFields[0] : null;
            return (firstField != null) ? NumberParser.ParseValue(firstField.DataType, Request[DefaultSearchFieldQueryKey], GetFormatter()) : null;
        }

        #endregion

        #region PreInit.

        protected override void OnPreInit(EventArgs e)
        {
            base.OnPreInit(e);

            if (string.IsNullOrEmpty(this.ProviderKey))
                throw new InvalidOperationException("Search provider/component key must be provided in the query string.");

            this.provider = SearchProviderRegistry.GetInstance().GetProvider(this.ProviderKey);
            if (this.provider == null)
                throw new InvalidOperationException("Cannot retrieve provider with " + this.ProviderKey + " key from the registry.");

            BuildEngineInstance();
            SetupDefaultSort();

            // Hide search controls if there are no search fields.
            if (this.engine.SearchFields.Count > 0)
            {
                BuildFilterComponent();
            }
            else
            {
                this.uiFilterRow.Style["display"] = "none";
                this.uiHrRow.Style["display"] = "none";
                this.uiFilterDisplayStyle.Value = "none";
            }

            BuildViewComponent();
            BuildCommandDisplayComponents();
        }

        private void BuildEngineInstance()
        {
            var engineBuilder = this.provider.CreateEngineBuilderInstance();
            if (engineBuilder == null)
                throw new Exception("Search provider/component does not contain a valid search engine builder.");
            engineBuilder.MessageSource.CurrentCulture = GetUiCulture();
            this.engine = engineBuilder.CreateSearchEngine(GetCurrentActor());

            // Setup engine ASAP. If filter is set then it's to late to set it up in PageLoad.
            SettingsUtil.SetupComponent(this, this.ProviderKey, "engine");
        }

        private void SetupDefaultSort()
        {
            this.defaultSortBy = null;
            SettingsUtil.SetupComponent(this, this.ProviderKey, "defaultSortBy");
            if (!string.IsNullOrEmpty(this.defaultSortBy) && this.engine != null)
            {
                this.engine.ClearSortExpressions();
                this.engine.AddSortExpression(this.defaultSortBy, /*asc*/ true);
            }
        }

        private void BuildFilterComponent()
        {
            this.uiFilter = this.provider.CreateFilterInstance();
            if (this.uiFilter != null)
            {
                Control ctlFilter = (Control)this.uiFilter;
                ctlFilter.ID = "uiFilter";
                this.uiFilterCell.Controls.Add(ctlFilter);

                this.uiFilter.Engine = this.engine;
                this.uiFilter.FilterCommand += new EventHandler<SearchFilterCommandEventArgs>(uiFilter_FilterCommand);
                this.uiFilter.Formatter = GetFormatter();
            }
        }

        private void BuildViewComponent()
        {
            this.uiView = this.provider.CreateViewInstance();
            if (this.uiView != null)
            {
                Control ctlView = (Control)this.uiView;
                ctlView.ID = "uiView";
                this.uiViewCell.Controls.Add(ctlView);

                this.uiView.Engine = this.engine;
                this.uiView.Formatter = GetFormatter();
                this.uiView.Actor = GetCurrentActor();
            }
        }

        private void BuildCommandDisplayComponents()
        {
            IList<ICommandDisplay> cmdDisplays = this.provider.CreateCommandDisplayInstances();
            foreach (ICommandDisplay display in cmdDisplays)
            {
                display.Actor = GetCurrentActor();
                display.Commands = this.uiView.Commands;
                if (display.Formatter == null)
                    display.Formatter = GetFormatter();

                if (display is CommandToolBar)
                {
                    this.uiCommandButtons = (CommandToolBar)display;
                    this.uiCommandButtons.ID = "uiCommandButtons";
                    this.uiCommandButtonsCell.Controls.Add(this.uiCommandButtons);

                    // Settings will be re-applied in Init to overload defaults in skins.
                    this.commandButtonsSettings = this.uiCommandButtons.GetSettings();
                }
                else if (display is CommandMenu && this.uiView is SearchView)
                {
                    // Setup as context-menu for the grid. Assigned in Init.
                    this.uiContextMenu = (CommandMenu)display;
                    this.uiContextMenu.ID = "uiContextMenu";
                    this.uiViewCell.Controls.Add(this.uiContextMenu);

                    // Settings will be re-applied in Init to overload defaults in skins.
                    this.contextMenuSettings = this.uiContextMenu.GetSettings();
                }
            }

            if (this.provider.QuickFilterEnabled && this.uiContextMenu != null)
                CreateQuickFilterCommands();
        }

        private static IActor GetCurrentActor()
        {
            return UserSession.Actor;
        }

        private static IMessageFormatter GetFormatter()
        {
            return UserSession.Formatter;
        }

        private static ITextSource GetTextSource()
        {
            return UserSession.MessageSource;
        }

        private static CultureInfo GetUiCulture()
        {
            return UserSession.UiCulture;
        }

        #endregion

        #region Init.

        private void Page_Init(object sender, System.EventArgs e)
        {
            OverrideSkinDefaults();
            // Quick filter must be assigned before menu iz assigned to view but after settings are applied (prev. line).
            AssignQuickFilterCommandsToContextMenu();
            // This will create menu nodes and generate command scripts.
            AssignContextMenuToSearchView();
            AssignAsyncPostBacksOnSelectionChanged();

            // Changes to filter must be performed before the fields are processed because the methods below cause CreateChildControls() to be invoked.
            SearchFilter filterWithSupportForControlSharing = this.uiFilter as SearchFilter;
            if (filterWithSupportForControlSharing != null)
                EnsureMultipleValuesAllowedForSharedFields(filterWithSupportForControlSharing);

            if (!IsPostBack)
            {
                ProcessFilterStateSpecifiedInUrl();
                ReadAndDisplayBeginValuesFromUrl();
                if (filterWithSupportForControlSharing != null)
                    EnsureSharedFieldsHaveEqualValues(filterWithSupportForControlSharing);

                ReadAndDisplaySelectedValueSetKeysFromUrl();
                ReadAndDisplayEndValuesFromUrl();
                ReadAndDisplayNegationsFromUrl();
            }

            // If PostBack only hidden and autopopulated fields are parsed.
            // Cannot be in previous !IsPostBack block because multiple values may not be properly displayed in filter generated controls.
            ReadAndDisplayNotInValuesFromUrl();
            ReadAndDisplayValuesFromUrl();
        }

        private void OverrideSkinDefaults()
        {
            if (this.uiFilter != null)
            {
                if (this.provider.FilterSettings != null)
                    this.uiFilter.ApplySettings(this.provider.FilterSettings);

                this.uiFilter.Formatter = GetFormatter();
            }

            if (this.uiView != null)
            {
                if (this.provider.ViewSettings != null)
                    this.uiView.ApplySettings(this.provider.ViewSettings);

                this.uiView.Formatter = GetFormatter();
            }

            if (this.uiCommandButtons != null && this.commandButtonsSettings != null)
                this.uiCommandButtons.ApplySettings(this.commandButtonsSettings);

            if (this.uiContextMenu != null && this.contextMenuSettings != null)
                this.uiContextMenu.ApplySettings(this.contextMenuSettings);
        }

        private void AssignContextMenuToSearchView()
        {
            if (this.uiContextMenu != null && this.uiView is SearchView)
            {
                SearchView searchView = (SearchView)this.uiView;
                searchView.RightClickEnabled = true;
                searchView.RightClickAction = this.uiContextMenu.ShowMenuJScript;
            }
        }

        private void AssignAsyncPostBacksOnSelectionChanged()
        {
            bool horizontalSecurityRequired = (this.uiCommandButtons != null && this.uiCommandButtons.HorizontalSecurityEnabled)
                || (this.uiContextMenu != null && this.uiContextMenu.HorizontalSecurityEnabled);

            if (horizontalSecurityRequired && this.uiView.Commands != null && this.uiView is SearchView)
            {
                bool doAsyncPostBackOnChange = false;
                foreach (ICommand cmd in this.uiView.Commands)
                {
                    if (cmd.RequiredParameters.Length > 0)
                        doAsyncPostBackOnChange = true;
                }

                if (doAsyncPostBackOnChange)
                {
                    SearchView searchView = (SearchView)this.uiView;
                    if (string.IsNullOrEmpty(searchView.OnChangeAction))
                        searchView.OnChangeAction = "DoAsyncPostBack();";
                    else
                        searchView.OnChangeAction += ";DoAsyncPostBack();";
                }
            }
        }

        private void ProcessFilterStateSpecifiedInUrl()
        {
            if (!string.IsNullOrEmpty(this.FilterState))
            {
                try
                {
                    SearchEngineFieldsState.Deserialize(engine.SearchFields, this.FilterState, true, true);
                    if (this.uiFilter != null)
                    {
                        this.uiFilter.ReadSearchFields();
                        this.searchResults = this.uiView.Search();
                    }
                }
                catch { }
            }
        }

        private void ReadAndDisplayBeginValuesFromUrl()
        {
            object firstFieldVal = GetDefaultFieldBeginValue();
            if (firstFieldVal != null)
                this.engine.SearchFields[0].BeginValue = firstFieldVal;

            foreach (KeyValuePair<string, string> fieldBeginValue in this.BeginValues)
            {
                ISearchEngineField searchField = engine.SearchFields[fieldBeginValue.Key];
                if (searchField != null)
                {
                    searchField.BeginValue = NumberParser.ParseValue(searchField.DataType, fieldBeginValue.Value, GetFormatter());
                }
            }
        }

        private void ReadAndDisplaySelectedValueSetKeysFromUrl()
        {
            foreach (KeyValuePair<string, string> fieldAndValueSetKey in this.SelectedValueSetKeys)
            {
                ISearchEngineField searchField = engine.SearchFields[fieldAndValueSetKey.Key];
                if (searchField != null)
                {
                    bool isValidValueSetKey = ArrayUtil.Exists<INamedValueSet>(searchField.ValueSets, (set) => (set.Key == fieldAndValueSetKey.Value));
                    searchField.SelectedValueSetKey = fieldAndValueSetKey.Value;
                }
            }
        }

        private void ReadAndDisplayNotInValuesFromUrl()
        {
            foreach (KeyValuePair<string, string[]> fieldAndValues in this.NotInValues)
            {
                ISearchEngineField searchField = engine.SearchFields[fieldAndValues.Key];
                if (searchField != null)
                {
                    // If PostBack only hidden and autopopulated fields are parsed.
                    bool shouldParse = (!IsPostBack) || (searchField.IsAutoPopulated || searchField.IsHidden);
                    if (shouldParse)
                    {
                        searchField.Negate = true;
                        searchField.Values = new object[fieldAndValues.Value.Length];
                        for (int idxVal = 0; idxVal < searchField.Values.Length; idxVal++)
                            searchField.Values[idxVal] = NumberParser.ParseValue(searchField.DataType, fieldAndValues.Value[idxVal], GetFormatter());
                    }
                }
            }
        }

        private void ReadAndDisplayEndValuesFromUrl()
        {
            foreach (KeyValuePair<string, string> fieldEndValue in this.EndValues)
            {
                ISearchEngineField searchField = engine.SearchFields[fieldEndValue.Key];
                if (searchField != null)
                {
                    searchField.EndValue = NumberParser.ParseValue(searchField.DataType, fieldEndValue.Value, GetFormatter());
                }
            }
        }

        private void ReadAndDisplayNegationsFromUrl()
        {
            foreach (KeyValuePair<string, bool> fieldNegate in this.fieldNegations)
            {
                ISearchEngineField searchField = engine.SearchFields[fieldNegate.Key];
                if (searchField != null)
                {
                    searchField.Negate = fieldNegate.Value;
                }
            }
        }

        private void ReadAndDisplayValuesFromUrl()
        {
            foreach (KeyValuePair<string, string[]> fieldAndValues in this.Values)
            {
                ISearchEngineField searchField = engine.SearchFields[fieldAndValues.Key];
                if (searchField != null)
                {
                    // If PostBack only hidden and autopopulated fields are parsed.
                    bool shouldParse = (!IsPostBack) || (searchField.IsAutoPopulated || searchField.IsHidden);
                    if (shouldParse)
                    {
                        searchField.Values = new object[fieldAndValues.Value.Length];
                        for (int idxVal = 0; idxVal < searchField.Values.Length; idxVal++)
                            searchField.Values[idxVal] = NumberParser.ParseValue(searchField.DataType, fieldAndValues.Value[idxVal], GetFormatter());
                    }
                }
            }
        }

        private static void EnsureSharedFieldsHaveEqualValues(SearchFilter filter)
        {
            string[][] combinations = filter.ControlSharing.GetCombinations();
            foreach (string[] sharedFields in combinations)
            {
                object beginValue = GetCommonSharedFieldBeginValue(filter.Engine, sharedFields);
                if (beginValue != null)
                    SetCommonSharedFieldValues(filter.Engine, sharedFields, beginValue);
            }
        }

        private static object GetCommonSharedFieldBeginValue(ISearchEngine engine, string[] sharedFields)
        {
            // Some of the shared field may bi inited, and some not.
            // The common value is the first inited. Others are ignored.            
            foreach (string fieldName in sharedFields)
            {
                ISearchEngineField field = engine.SearchFields[fieldName];
                object beginValue = (field != null) ? field.BeginValue : null;
                if (beginValue != null)
                    return beginValue;
            }

            return null;
        }

        private static void SetCommonSharedFieldValues(ISearchEngine engine, string[] sharedFields, object beginValue)
        {
            // The value(s) specified in URL are splitted to create a "smart" filter.
            foreach (string fieldName in sharedFields)
            {
                ISearchEngineField field = engine.SearchFields[fieldName];
                object[] values = null;
                if (field != null)
                {
                    if (values == null)
                        values = NumberParser.ParseArray(field.DataType, Convert.ToString(beginValue).Trim(), " ", GetFormatter());

                    field.Values = values;
                    field.SelectedTextSearchMode = TextSearchMode.BeginningOfText;
                    // Use OR if only one value was parsed. Otherwise, no matches would be found.
                    field.AddWithOr = (values.Length <= 1);
                }
            }
        }

        private static void EnsureMultipleValuesAllowedForSharedFields(SearchFilter filter)
        {
            if (!filter.AnyWordOrPhraseEnabled)
            {
                foreach (ISearchEngineField field in filter.Engine.SearchFields)
                    field.AllowMultipleValues = false;
            }

            filter.AnyWordOrPhraseEnabled = true;
            foreach (string[] sharedFields in filter.ControlSharing.GetCombinations())
            {
                foreach (string fieldName in sharedFields)
                {
                    ISearchEngineField field = filter.Engine.SearchFields[fieldName];
                    if (field != null)
                        field.AllowMultipleValues = true;
                }
            }
        }

        #endregion

        #region Load.

        private void Page_Load(object sender, System.EventArgs e)
        {
            WebUtil.DontCacheCurrentPageOnBrowser();
            if (!IsPostBack && string.IsNullOrEmpty(this.FilterState))
            {
                if (!this.provider.FilterDisplayed)
                    this.uiFilterDisplayStyle.Value = "none";
            }

            if (IsPostBack)
            {
                if (this.uiFilter != null)
                    this.uiFilter.WriteToSearchFields();
            }

            SettingsUtil.SetupModule(this, this.ProviderKey);
            this.uiFilterRow.Style["display"] = this.uiFilterDisplayStyle.Value;
            this.uiHrRow.Style["display"] = this.uiFilterDisplayStyle.Value;
            FocusCurrentWindow();
            SetTitleAndCaption();

            this.uiDoAsyncPostBackBtn.Style["display"] = "none";
            this.FormFooter1.GoToTopVisible = true;
        }

        private void FocusCurrentWindow()
        {
            // Reqired if search form is openned in a popup window.
            ClientScript.RegisterStartupScript(GetType(), "focus", "window.focus();", true);
        }

        const string switchFilterImg = "<img src='{1}/buttons/fltr_on_b.gif' alt='{0}' onclick='SwitchFilterVisibility();' class='filter_toolbar' />&nbsp;";

        private static string ImagesUrl
        {
            get { return HttpContext.Current.Request.ApplicationPath + "/Images"; }
        }

        private void SetTitleAndCaption()
        {
            this.Title = this.provider.Caption;
            if (this.uiFilter != null)
            {
                this.uiFilterHeader.Visible = true;
            }

            this.uiCaptionLbl.Text = GetTitle();
        }

        private string GetTitle()
        {
            return string.IsNullOrEmpty(this.engine.SearchFields.Name) ? this.provider.Caption : this.engine.SearchFields.Name;
        }

        #endregion

        #region Search, BuildSearchUrlWithFilterState.

        private void Search()
        {
            const int maxUrlLength = 1000;
            string searchUrl = BuildSearchUrlWithFilterState();
            if (searchUrl.Length < maxUrlLength)
                Response.Redirect(searchUrl);
            else
                this.searchResults = this.uiView.Search();
        }

        private string BuildSearchUrlWithFilterState()
        {
            // Keep all query keys except initial searchField values and previous filterState.
            StringBuilder url = new StringBuilder();
            url.Append(this.TemplateSourceDirectory + "/LookupFrm.aspx");
            url.Append("?");
            url.Append(QueryKeys.FilterState);
            url.Append("=");
            url.Append(HttpUtility.UrlEncode(HttpUtility.UrlEncode(SearchEngineFieldsState.Serialize(this.engine.SearchFields))));

            string[] queryKeys = Request.QueryString.AllKeys;
            foreach (string key in queryKeys)
            {
                if (string.IsNullOrEmpty(key) || key == QueryKeys.FilterState || key == DefaultSearchFieldQueryKey || key.StartsWith(searchFieldDot))
                    continue;

                url.Append("&");
                url.Append(key);
                url.Append("=");
                url.Append(HttpUtility.UrlEncode(Request.QueryString[key]));
            }

            return url.ToString();
        }

        #endregion

        #region FilterCommand.

        void uiFilter_FilterCommand(object sender, SearchFilterCommandEventArgs e)
        {
            bool filterChanged = (e.Commands & SearchFilterCommands.FilterStateChanged) > 0;
            bool search = (e.Commands & SearchFilterCommands.Search) > 0;
            bool reset = (e.Commands & SearchFilterCommands.Reset) > 0;
            bool saveForActor = (e.Commands & SearchFilterCommands.SaveFilterForActor) > 0;

            if (saveForActor)
            {
                SaveFilterState();
            }

            if (filterChanged || search || reset)
            {
                Search();
                return;
            }
        }

        private void SaveFilterState()
        {
            string filterName = this.uiFilterName.Value.Trim();
            if (filterName.Length == 0)
                throw new InvalidOperationException("Filter's name must be set before it can be saved.");

            bool alreadyExists = (this.uiFilter != null) && this.uiFilter.PredefinedSearchEngineFieldsStates.ContainsKey(filterName);
            if (alreadyExists)
            {
                this.AlertWarning = this.uiFilterAlreadyExistsCaption.Text;
            }
            else
            {
                string propertyPath = "PredefinedSearchEngineFieldsStates/" + filterName;
                this.engine.SearchFields.Name = filterName;
                object filterState = SearchEngineFieldsState.Serialize(this.engine.SearchFields);
                SettingsUtil.SaveUserSetting(this, this.ProviderKey, "uiFilter", propertyPath, filterState);
            }
        }

        #endregion

        #region SearchFilter Toolbar.

        const string searchFilterTemplate = "<img src='{1}/buttons/SearchRefresh16.gif' onclick='document.getElementById(\"uiFilterSearch\").click();' alt='{0}' class='filter_toolbar' />";
        const string resetFilterTemplate = "<img src='{1}/buttons/FilterOff16.gif' onclick='document.getElementById(\"uiFilterReset\").click();' alt='{0}' class='filter_toolbar' />";
        const string applyFilterTemplate = "<img src='{1}/buttons/Checkmark16.gif' onclick=\"document.getElementById('uiFilterSelectedBtn').click(); \" alt='{0}' class='filter_toolbar' />";
        const string saveFilterTemplate = "<img src='{1}/buttons/Floppy16.gif' onclick='if (SaveFilter()) document.getElementById(\"uiFilterSave\").click();' alt='{0}' class='filter_toolbar' />";
        const string deleteFilterTemplate = "<img src='{1}/buttons/WireTrash16.gif' onclick=\"document.getElementById('uiFilterDeleteBtn').click(); \" alt='{0}' class='filter_toolbar' />";
        const string openOptions = "window.open('SearchOptionsFrm.aspx?providerKey={0}&guid={1}', 'SearchOptionsFrm', 'width=500,height=700,top=100,left=200,status=no,resizable=yes');";

        private void DecorateSearchFilter()
        {
            SearchFilter filter = (this.uiFilter != null) ? (this.uiFilter as SearchFilter) : null;
            if (filter == null)
                return;

            if (filter.SearchButtonVisible)
            {
                this.uiSearchButton.Visible = true;
                this.uiSearchButton.Title = filter.SearchButtonText;
            }

            if (filter.ResetButtonVisible)
            {
                this.uiFilterResetButton.Visible = true;
                this.uiFilterResetButton.Title = filter.ResetButtonText;
                filter.ResetButtonVisible = false;
            }

            if (filter.OpenPredefinedFilterVisible)
            {
                if (filter.PredefinedSearchEngineFieldsStates.Count > 0)
                {
                    DisplayFiltersInToolBar(filter);
                    this.uiPredefinedFilterHeader.Visible = Visible;
                }

                filter.OpenPredefinedFilterVisible = false;
            }

            if (filter.SaveButtonVisible)
            {
                filter.SaveButtonVisible = false;
                this.uiSaveFilterHeader.Title = filter.SaveButtonText;
                this.uiSaveFilterHeader.Visible = true;
            }
        }

        private void DisplayFiltersInToolBar(SearchFilter filter)
        {
            StringBuilder predefinedFiltersHtmCode = new StringBuilder();
            List<string> allFilters = new List<string>();
            foreach (KeyValuePair<string, string> filterStateAndName in filter.PredefinedSearchEngineFieldsStates)
                allFilters.Add(filterStateAndName.Key);

            List<string> globalFilters, userFilters;
            SortGlobalAndUserFilters(allFilters, out globalFilters, out userFilters);
            this.uiOpenPredefinedFilterCaption.Text = filter.OpenPredefinedFilterText;

            foreach (string filterName in globalFilters)
                AddDdlFilterItem(predefinedFiltersHtmCode, filterName, filterName, filter);

            if (globalFilters.Count > 0 && userFilters.Count > 0)
                predefinedFiltersHtmCode.Append("<li></li>");

            foreach (string filterName in userFilters)
                AddDdlFilterItem(predefinedFiltersHtmCode, filterName, filterName, filter);

            this.uiDropDown.InnerHtml = predefinedFiltersHtmCode.ToString();
        }

        private void AddDdlFilterItem(StringBuilder predefinedFiltersHtmCode, string itemText, string itemValue, SearchFilter filter)
        {
            predefinedFiltersHtmCode.Append("<li><span class=\"left\">");
            predefinedFiltersHtmCode.Append(itemText);
            predefinedFiltersHtmCode.Append("</span><span class=\"right\">");

            string deleteFilterTooltip = UserSession.MessageSource.GetString("SearchFrm_DeleteFilterTooltip");
            if (string.IsNullOrEmpty(deleteFilterTooltip))
                deleteFilterTooltip = this.uiDeleteFilterTooltipCaption.Text;

            predefinedFiltersHtmCode.Append("<a href=\"#\" title=\"" + filter.OpenPredefinedFilterText + "\"  onClick=\"document.getElementById('uiFilterSelectedTxt').value='" + HttpUtility.UrlEncode(itemValue) + "'; document.getElementById('uiFilterSelectedBtn').click(); \" class=\"Button Add left\"><span>Add</span></a>");
            predefinedFiltersHtmCode.Append("<a href=\"#\" title=\"" + deleteFilterTooltip + "\" onClick=\"document.getElementById('uiFilterSelectedTxt').value='" + HttpUtility.UrlEncode(itemValue) + "'; document.getElementById('uiFilterDeleteBtn').click(); \" class=\"Button Delete left\"><span>Delete</span></a>");
            predefinedFiltersHtmCode.Append("</span></li>");
        }

        private bool IsGlobalFilter(string filterName)
        {
            IAppSetting filter = GetFilter(filterName);
            bool isGlobal = (filter != null) && (filter.ActorId == null);
            return isGlobal;
        }

        private List<string> SortAllFilters(IList<string> allFilters)
        {
            List<string> sortedFilters = new List<string>(allFilters);
            sortedFilters.Sort();
            return sortedFilters;
        }

        private void SortGlobalAndUserFilters(List<string> allFilters, out List<string> globalFilters, out List<string> userFilters)
        {
            globalFilters = new List<string>();
            userFilters = new List<string>();
            foreach (string filterName in allFilters)
            {
                if (IsGlobalFilter(filterName))
                    globalFilters.Add(filterName);
                else
                    userFilters.Add(filterName);
            }
        }

        private IAppSetting GetFilter(string filterName)
        {
            IAppSetting criteria = SettingsUtil.Manager.NewSetting();
            criteria.ActorId = SettingsUtil.ActorProvider();
            criteria.Application = SettingsUtil.CurrentApplication;
            criteria.Component = "uiFilter";
            criteria.Context = this.ProviderKey;
            criteria.Module = GetType().ToString();
            criteria.PropertyPathString = "PredefinedSearchEngineFieldsStates/" + filterName;

            IList<IAppSetting> matches = SettingsUtil.Manager.Find(criteria);
            IAppSetting filter = (matches.Count == 1) ? matches[0] : null;
            return filter;
        }

        protected void uiFilterSearch_ServerClick(object sender, EventArgs e)
        {
            SearchFilter filter = (this.uiFilter != null) ? (this.uiFilter as SearchFilter) : null;
            if (filter != null)
                filter.Search();
        }

        protected void uiFilterReset_ServerClick(object sender, EventArgs e)
        {
            SearchFilter filter = (this.uiFilter != null) ? (this.uiFilter as SearchFilter) : null;
            if (filter != null)
                filter.Reset();
        }

        protected void uiFilterSave_ServerClick(object sender, EventArgs e)
        {
            SearchFilter filter = (this.uiFilter != null) ? (this.uiFilter as SearchFilter) : null;
            if (filter != null)
                filter.SaveFilterForActor();
        }

        protected void uiFilterSelectedBtn_ServerClick(object sender, EventArgs e)
        {
            string filterName = HttpUtility.UrlDecode(this.uiFilterSelectedTxt.Value.Trim());
            string filterState;
            bool filterFound = this.uiFilter.PredefinedSearchEngineFieldsStates.TryGetValue(filterName, out filterState);
            if (!filterFound)
                filterState = "";

            SearchFilter filter = (this.uiFilter != null) ? (this.uiFilter as SearchFilter) : null;
            if (filter != null)
                filter.SelectPredefinedFilter(filterState);
        }

        protected void Page_PreRender(object sender, EventArgs e)
        {
            if (Session["SearchFrm_FilterState"] != null)
            {
                string filterName = Session["SearchFrm_FilterState"].ToString();
                Session["SearchFrm_FilterState"] = null;
                string filterState;
                bool filterFound = this.uiFilter.PredefinedSearchEngineFieldsStates.TryGetValue(filterName, out filterState);
                if (!filterFound)
                    filterState = "";

                SearchFilter filter = (this.uiFilter != null) ? (this.uiFilter as SearchFilter) : null;
                if (filter != null)
                    filter.SelectPredefinedFilter(filterState);
            }
        }

        protected void uiFilterDeleteBtn_ServerClick(object sender, EventArgs e)
        {
            string filterName = HttpUtility.UrlDecode(this.uiFilterSelectedTxt.Value.Trim());
            if (filterName.Length > 0)
            {
                IAppSetting setting = SettingsUtil.Manager.NewSetting();
                setting.ActorId = SettingsUtil.ActorProvider().Id;
                setting.Application = SettingsUtil.CurrentApplication;
                setting.Component = "uiFilter";
                setting.Context = this.ProviderKey;
                setting.Module = GetType().ToString();
                string propertyPath = "PredefinedSearchEngineFieldsStates/" + filterName;
                setting.PropertyPathString = propertyPath;

                SettingsUtil.Manager.Delete(setting);
                this.uiFilterSelectedTxt.Value = "";
            }

            SearchFilter filter = (this.uiFilter != null) ? (this.uiFilter as SearchFilter) : null;
            if (filter != null)
                filter.Search();
        }

        #endregion

        /// <summary>Create decorated filter toolbar.</summary>
        protected override void Render(HtmlTextWriter writer)
        {
            // SearchFilter ensures that captions are set in its PreRender stage so we wait until Render() is called to process it.
            if (this.uiFilter != null)
                DecorateSearchFilter();

            base.Render(writer);
        }

        #region OnPreRender.

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            // Whenever selection changes DoAsyncPostBack function is invoked.
            // All commands are enabled while the request is beeing processed in the background.
            // Some commands will be disabled once the server responds.
            this.JsEnableAllCommands = "";
            if (this.uiCommandButtons != null)
                this.JsEnableAllCommands += this.uiCommandButtons.EnableAllCommandsScript;
            if (this.uiContextMenu != null)
                this.JsEnableAllCommands += this.uiContextMenu.EnableAllCommandsScript;

            AutoExecuteDefaultCommandIfExactlyOneMatchIsFound();
            PrepareReportData();

            if (this.AlertWarning != null)
            {
                JScript.Alert(this.AlertWarning);
                this.AlertWarning = null;
            }

            DisplayLegend();
            TryFocusFirstFilterControl();
        }

        private string AlertWarning
        {
            get { return Session["SearchFrm.AlertWarning"] as string; }
            set { Session["SearchFrm.AlertWarning"] = value; }
        }

        #endregion

        #region Legend.

        /// <summary>SearchProvider.ExtendedProperties["Legend"] property may contain optional image src displayed at the bottom of the search page.</summary>
        public const string Legend = "Legend";

        private void DisplayLegend()
        {
            object legendSrc;
            this.provider.ExtendedProperties.TryGetValue(Legend, out legendSrc);
            this.uiLegend.Visible = (legendSrc != null);
            this.uiLegend.ImageUrl = Convert.ToString(legendSrc);
        }

        #endregion

        #region AutoExecuteDefaultCommandIfExactlyOneMatchIsFound.

        /// <summary>Set any value to SearchProvider.ExtendedProperties["AutoExecuteDefaultCommandDisabled"] property to prevent automatic execution of default command when excatly one match is found.</summary>
        public const string AutoExecuteDefaultCommandDisabled = "AutoExecuteDefaultCommandDisabled";

        private void AutoExecuteDefaultCommandIfExactlyOneMatchIsFound()
        {
            if (this.provider.ExtendedProperties.ContainsKey(AutoExecuteDefaultCommandDisabled))
                return;

            bool noClientFilter = (GetDefaultFieldBeginValue() == null) && (this.BeginValues.Count == 0);
            if (noClientFilter)
                return;

            // At least one of the begin values must be for a non-autopopulated, non-hidden field.
            bool isUserEntry = false;
            foreach (KeyValuePair<string, string> fieldAndVal in this.BeginValues)
            {
                ISearchEngineField field = this.engine.SearchFields[fieldAndVal.Key] as ISearchEngineField;
                isUserEntry = (field != null) && !(field.IsHidden || field.IsAutoPopulated);
                if (isUserEntry)
                    break;
            }

            // Check default field, too.
            if (!isUserEntry && (GetDefaultFieldBeginValue() != null))
            {
                ISearchEngineField field = this.engine.SearchFields[0];
                isUserEntry = !(field.IsHidden || field.IsAutoPopulated);
            }

            if (!isUserEntry)
                return;

            SearchView view = this.uiView as SearchView;
            if (view != null && view.Commands.Count > 0)
            {
                if (this.searchResults == null)
                    this.searchResults = view.Search();

                if (this.searchResults.Count == 1)
                {
                    int idxDefaultCmd = 0;
                    for (int idxCommand = 0; idxCommand < view.Commands.Count; idxCommand++)
                    {
                        if (view.Commands[idxCommand].Key == view.DefaultCommandKey)
                        {
                            idxDefaultCmd = idxCommand;
                            break;
                        }
                    }

                    ICommand defaultCmd = view.Commands[idxDefaultCmd];
                    view.SelectedIndexes = new int[] { 0 };
                    bool canBeExecuted = defaultCmd.PrepareForExecution();
                    if (canBeExecuted)
                        defaultCmd.Execute(view, view.Actor);
                }
            }
        }

        #endregion

        #region PrepareReportData.

        private void PrepareReportData()
        {
            if (this.searchResults == null)
                this.searchResults = this.uiView.GetData();

            DataTable matrix = new DataTable();
            matrix.Columns.Add("rownum", typeof(int));
            matrix.Columns.Add("caption", typeof(string));
            matrix.Columns.Add("value", typeof(string));
            for (int rownum = 0; rownum < searchResults.Count; rownum++)
            {
                DataRowView row = this.searchResults[rownum];
                foreach (IBindableField displayField in SortDisplayFields())
                {
                    bool skippedBecauseItsHiddenOrNoCaption = displayField.IsHidden || string.IsNullOrEmpty(displayField.Caption);
                    if (!skippedBecauseItsHiddenOrNoCaption)
                    {
                        string caption = !string.IsNullOrEmpty(displayField.Caption) ? displayField.Caption : displayField.BindingName;
                        object fieldValue = row[displayField.BindingName];
                        string formattedValue = (this.uiView.Formatter != null) ? this.uiView.Formatter.Format(fieldValue) : Convert.ToString(fieldValue);
                        matrix.Rows.Add(rownum, caption, formattedValue);
                    }
                }
            }

            SearchReportFrm.Matrix = matrix;
            SearchReportFrm.Caption = GetTitle();
        }

        private IEnumerable SortDisplayFields()
        {
            // FieldPermissions with default ordinal are appended at the end of collection in default order.
            ArrayList sortedFields = new ArrayList();
            foreach (IBindableField displayField in this.engine.DisplayFields)
            {
                if (displayField.Ordinal != default(int))
                    sortedFields.Add(displayField);
            }

            sortedFields = ArrayUtil.Sort(sortedFields, "Ordinal");
            foreach (IBindableField displayField in this.engine.DisplayFields)
            {
                if (displayField.Ordinal == default(int))
                    sortedFields.Add(displayField);
            }

            return sortedFields;
        }

        #endregion

        #region TryFocusFirstFilterControl.

        private void TryFocusFirstFilterControl()
        {
            SearchFilter filter = this.uiFilter as SearchFilter;
            if (filter == null)
                return;

            bool isFilterCreated = (filter.Controls.Count > 0);
            if (isFilterCreated)
                FocusFirstFilterControl(filter);
            else
                filter.PreRender += delegate(object o, EventArgs ea) { FocusFirstFilterControl(filter); };
        }

        private void FocusFirstFilterControl(SearchFilter filter)
        {
            ISearchEngineField searchField = GetFirstEditableSearchField();
            if (searchField != null)
            {
                Control fieldCtl = filter.GetControl(searchField, false);
                if (fieldCtl != null)
                    this.uiFirstFilterControlId.Value = fieldCtl.ClientID;
            }
        }

        private ISearchEngineField GetFirstEditableSearchField()
        {
            foreach (ISearchEngineField field in this.engine.SearchFields)
            {
                bool isEditable = !field.IsAutoPopulated && !field.IsHidden && !field.IsReadOnly;
                if (isEditable)
                    return field;
            }

            return null;
        }

        #endregion

        #region Quick contextmenu filter.

        const string FilterCmdKeyFormat = "QuickFilterBy{0}";
        CommandGroupStyle quickFilterCommandGroup;
        CommandGroupStyle cmdHierarchyWoQuickFilter;

        private void CreateQuickFilterCommands()
        {
            if (this.uiContextMenu == null)
                return;

            var quickFilterFields = GetQuickFilterFields(this.engine, this.uiFilter as SearchFilter);
            if (quickFilterFields.Count == 0)
                return;

            if (this.uiCommandButtons != null)
            {
                // Ensure that quick filter commands are not added to toolbar.
                bool noCustomCommandHierarchy = this.uiCommandButtons.AutoGenerateCommandHierarchy || (this.uiCommandButtons.CommandHierarchy.Items.Count == 0);
                if (noCustomCommandHierarchy)
                    this.uiCommandButtons.CommandHierarchy = new CommandGroupStyle(this.uiCommandButtons.Commands, this.uiCommandButtons.MinParameterSetSize, this.uiCommandButtons.MaxParameterSetSize);
            }

            this.cmdHierarchyWoQuickFilter = new CommandGroupStyle(this.uiContextMenu.Commands, this.uiContextMenu.MinParameterSetSize, this.uiContextMenu.MaxParameterSetSize);
            this.quickFilterCommandGroup = new CommandGroupStyle("QuickFilter", this.provider.QuickFilterCaption ?? "Filter", null);
            foreach (IBindableField field in quickFilterFields)
            {
                string key = string.Format(FilterCmdKeyFormat, field.BindingName);
                string fieldCaption = field.Caption ?? this.engine.SearchFields[field.BindingName].Caption;
                FilterCommand cmd = new FilterCommand(key, fieldCaption, field.BindingName);
                cmd.SetStaticGetFilterMethod(GetFilter);
                this.uiView.Commands.Add(cmd);
                this.quickFilterCommandGroup.Items.Add(new CommandGroupItem(new CommandStyle(cmd)));
            }
        }

        private void AssignQuickFilterCommandsToContextMenu()
        {
            if (this.quickFilterCommandGroup == null)
                return;

            if (!string.IsNullOrEmpty(this.uiContextMenu.CommandHierarchySrc))
            {
                // Ensure that quick filter items are not overwritten with items specified in XML.
                string xmlFilePath = MapPath(this.uiContextMenu.CommandHierarchySrc);
                this.uiContextMenu.CommandHierarchySrc = null;
                this.uiContextMenu.CommandHierarchy.ReadXmlFile(xmlFilePath);
                this.uiContextMenu.CommandHierarchy.Items.Add(new CommandGroupItem(this.quickFilterCommandGroup));
            }
            else if (this.uiContextMenu.CommandHierarchy.Items.Count > 0)
            {
                // Append quick filter items to existing items.
                this.uiContextMenu.CommandHierarchy.Items.Add(new CommandGroupItem(this.quickFilterCommandGroup));
            }
            else
            {
                this.uiContextMenu.CommandHierarchy = this.cmdHierarchyWoQuickFilter;
                this.uiContextMenu.CommandHierarchy.Items.Add(new CommandGroupItem(this.quickFilterCommandGroup));
            }
        }

        private static List<IBindableField> GetQuickFilterFields(ISearchEngine engine, SearchFilter filter)
        {
            var editableFilterFields = ArrayUtil.FindAll<List<ISearchEngineField>, ISearchEngineField>(engine.SearchFields, (field) => IsUserInputAllowed(field, filter));
            List<IBindableField> matches = new List<IBindableField>();
            foreach (IBindableField displayField in engine.DisplayFields)
            {
                bool hasMatchingFilterField = ArrayUtil.Exists<ISearchEngineField>(editableFilterFields, (filterField) => IsMatch(displayField, filterField));
                if (hasMatchingFilterField)
                    matches.Add(displayField);
            }
            return matches;
        }

        private static bool IsUserInputAllowed(ISearchEngineField field, SearchFilter filter)
        {
            bool isAllowedByEngine = field.AllowArbitraryBeginValue && !field.IsAutoPopulated && !field.IsHidden && !field.IsReadOnly;
            if (!isAllowedByEngine)
                return false;

            if (filter != null)
            {
                object isHidden = filter.SearchFieldProperties[field.BindingName, "IsHidden"];
                if (isHidden != null && isHidden.ToString() == true.ToString())
                    return false;

                object isAutoPopulated = filter.SearchFieldProperties[field.BindingName, "IsAutoPopulated"];
                if (isAutoPopulated != null && isAutoPopulated.ToString() == true.ToString())
                    return false;

                object allowArbitraryBeginValue = filter.SearchFieldProperties[field.BindingName, "AllowArbitraryBeginValue"];
                if (allowArbitraryBeginValue != null && allowArbitraryBeginValue.ToString() == false.ToString())
                    return false;

                object isReadOnly = filter.SearchFieldProperties[field.BindingName, "IsReadOnly"];
                if (isReadOnly != null && isReadOnly.ToString() == true.ToString())
                    return false;
            }

            return true;
        }

        private static bool IsMatch(IBindableField displayField, ISearchEngineField filterField)
        {
            bool equalNameAndType = (displayField.BindingName == filterField.BindingName) && (displayField.DataType == filterField.DataType);
            return equalNameAndType;
        }

        public class FilterCommand : WebCommandBase
        {
            Type getFilterClass;
            string getFilterStaticMethodName;

            protected FilterCommand()
            {
                this.MinParameterSetSize = 1;
                this.MaxParameterSetSize = 1;
            }

            public FilterCommand(string key, string text, string parameterName)
                : base(key, text, new string[] { parameterName })
            {
                this.MinParameterSetSize = 1;
                this.MaxParameterSetSize = 1;
            }

            public void SetStaticGetFilterMethod(Delegates.Function<ISearchFilter> getFilter)
            {
                if (!getFilter.Method.IsStatic)
                    throw new ArgumentException("The method must be static.");

                this.getFilterClass = getFilter.Method.DeclaringType;
                this.getFilterStaticMethodName = getFilter.Method.Name;
            }

            private ISearchFilter GetFilter()
            {
                return ReflectionUtil.InvokeStaticMethod(this.getFilterClass, this.getFilterStaticMethodName, new object[0]) as ISearchFilter;
            }

            public override void Execute(object caller, IActor actor)
            {
                ISearchFilter filter = GetFilter();
                ISearchView view = caller as ISearchView;
                if (filter == null || view == null)
                    return;

                EnsureParameters(true);
                if (this.Parameters.Count > 1)
                    throw new Exception("Multiple records not supported.");

                string fieldName = this.RequiredParameters[0];
                filter.Engine.SearchFields.ResetAllFields(/*don't reset auto-populated and hidden fields*/ false, false);
                ISearchEngineField field = filter.Engine.SearchFields[fieldName];
                object beginValue = GetParameterValue(0, fieldName);
                if (beginValue != null)
                {
                    field.BeginValue = beginValue;
                    field.IsNull = false;
                }
                else
                {
                    field.IsNull = true;
                }

                filter.ReadSearchFields();
                SearchFilter searchFilter = filter as SearchFilter;
                if (searchFilter != null)
                    searchFilter.Search();
                else
                    view.Search();
            }

            public override object Clone()
            {
                if (GetType() != typeof(FilterCommand))
                    throw new InvalidOperationException("Clone method must be overriden in derived classes. Do not use \"base.Clone()\" in derived class.");

                FilterCommand clone = new FilterCommand();
                clone.CopyFrom(this);
                return clone;
            }

            protected virtual void CopyFrom(FilterCommand source)
            {
                base.CopyFrom(source);
                this.getFilterClass = source.getFilterClass;
                this.getFilterStaticMethodName = source.getFilterStaticMethodName;
            }
        }

        #endregion

        private static ISearchFilter GetFilter()
        {
            LookupFrm frm = HttpContext.Current.Handler as LookupFrm;
            if (frm == null)
                return null;

            return frm.uiFilter;
        }
    }
}